/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.mail.imap.protocol;

import java.util.Vector;
import javax.mail.internet.ParameterList;
import com.sun.mail.iap.*;
import com.sun.mail.util.PropUtil;

/**
 * A BODYSTRUCTURE response.
 * 
 * @author John Mani
 * @author Bill Shannon
 */

public class BODYSTRUCTURE implements Item {

	static final char[] name = { 'B', 'O', 'D', 'Y', 'S', 'T', 'R', 'U', 'C', 'T', 'U', 'R', 'E' };
	public int msgno;

	public String type; // Type
	public String subtype; // Subtype
	public String encoding; // Encoding
	public int lines = -1; // Size in lines
	public int size = -1; // Size in bytes
	public String disposition; // Disposition
	public String id; // Content-ID
	public String description; // Content-Description
	public String md5; // MD-5 checksum
	public String attachment; // Attachment name
	public ParameterList cParams; // Body parameters
	public ParameterList dParams; // Disposition parameters
	public String[] language; // Language
	public BODYSTRUCTURE[] bodies; // array of BODYSTRUCTURE objects
	// for multipart & message/rfc822
	public ENVELOPE envelope; // for message/rfc822

	private static int SINGLE = 1;
	private static int MULTI = 2;
	private static int NESTED = 3;
	private int processedType; // MULTI | SINGLE | NESTED

	// special debugging output to debug parsing errors
	private static boolean parseDebug = PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);

	public BODYSTRUCTURE(FetchResponse r) throws ParsingException {
		if (parseDebug)
			System.out.println("DEBUG IMAP: parsing BODYSTRUCTURE");
		msgno = r.getNumber();
		if (parseDebug)
			System.out.println("DEBUG IMAP: msgno " + msgno);

		r.skipSpaces();

		if (r.readByte() != '(')
			throw new ParsingException("BODYSTRUCTURE parse error: missing ``('' at start");

		if (r.peekByte() == '(') { // multipart
			if (parseDebug)
				System.out.println("DEBUG IMAP: parsing multipart");
			type = "multipart";
			processedType = MULTI;
			Vector v = new Vector(1);
			int i = 1;
			do {
				v.addElement(new BODYSTRUCTURE(r));
				/*
				 * Even though the IMAP spec says there can't be any spaces
				 * between parts, some servers erroneously put a space in
				 * here. In the spirit of "be liberal in what you accept",
				 * we skip it.
				 */
				r.skipSpaces();
			} while (r.peekByte() == '(');

			// setup bodies.
			bodies = new BODYSTRUCTURE[v.size()];
			v.copyInto(bodies);

			subtype = r.readString(); // subtype
			if (parseDebug)
				System.out.println("DEBUG IMAP: subtype " + subtype);

			if (r.readByte() == ')') { // done
				if (parseDebug)
					System.out.println("DEBUG IMAP: parse DONE");
				return;
			}

			// Else, we have extension data

			if (parseDebug)
				System.out.println("DEBUG IMAP: parsing extension data");
			// Body parameters
			cParams = parseParameters(r);
			if (r.readByte() == ')') { // done
				if (parseDebug)
					System.out.println("DEBUG IMAP: body parameters DONE");
				return;
			}

			// Disposition
			byte b = r.readByte();
			if (b == '(') {
				if (parseDebug)
					System.out.println("DEBUG IMAP: parse disposition");
				disposition = r.readString();
				if (parseDebug)
					System.out.println("DEBUG IMAP: disposition " + disposition);
				dParams = parseParameters(r);
				if (r.readByte() != ')') // eat the end ')'
					throw new ParsingException("BODYSTRUCTURE parse error: " + "missing ``)'' at end of disposition in multipart");
				if (parseDebug)
					System.out.println("DEBUG IMAP: disposition DONE");
			} else if (b == 'N' || b == 'n') {
				if (parseDebug)
					System.out.println("DEBUG IMAP: disposition NIL");
				r.skip(2); // skip 'NIL'
			} else {
				throw new ParsingException("BODYSTRUCTURE parse error: " + type + "/" + subtype + ": " + "bad multipart disposition, b " + b);
			}

			// RFC3501 allows no body-fld-lang after body-fld-disp,
			// even though RFC2060 required it
			if ((b = r.readByte()) == ')') {
				if (parseDebug)
					System.out.println("DEBUG IMAP: no body-fld-lang");
				return; // done
			}

			if (b != ' ')
				throw new ParsingException("BODYSTRUCTURE parse error: " + "missing space after disposition");

			// Language
			if (r.peekByte() == '(') { // a list follows
				language = r.readStringList();
				if (parseDebug)
					System.out.println("DEBUG IMAP: language len " + language.length);
			} else {
				String l = r.readString();
				if (l != null) {
					String[] la = { l };
					language = la;
					if (parseDebug)
						System.out.println("DEBUG IMAP: language " + l);
				}
			}

			// RFC3501 defines an optional "body location" next,
			// but for now we ignore it along with other extensions.

			// Throw away any further extension data
			while (r.readByte() == ' ')
				parseBodyExtension(r);
		} else { // Single part
			if (parseDebug)
				System.out.println("DEBUG IMAP: single part");
			type = r.readString();
			if (parseDebug)
				System.out.println("DEBUG IMAP: type " + type);
			processedType = SINGLE;
			subtype = r.readString();
			if (parseDebug)
				System.out.println("DEBUG IMAP: subtype " + subtype);

			// SIMS 4.0 returns NIL for a Content-Type of "binary", fix it here
			if (type == null) {
				type = "application";
				subtype = "octet-stream";
			}
			cParams = parseParameters(r);
			if (parseDebug)
				System.out.println("DEBUG IMAP: cParams " + cParams);
			id = r.readString();
			if (parseDebug)
				System.out.println("DEBUG IMAP: id " + id);
			description = r.readString();
			if (parseDebug)
				System.out.println("DEBUG IMAP: description " + description);
			/*
			 * XXX - Work around bug in Exchange 2010 that
			 * returns unquoted string.
			 */
			encoding = r.readAtomString();
			if (encoding != null && encoding.equalsIgnoreCase("NIL"))
				encoding = null;
			if (parseDebug)
				System.out.println("DEBUG IMAP: encoding " + encoding);
			size = r.readNumber();
			if (parseDebug)
				System.out.println("DEBUG IMAP: size " + size);
			if (size < 0)
				throw new ParsingException("BODYSTRUCTURE parse error: bad ``size'' element");

			// "text/*" & "message/rfc822" types have additional data ..
			if (type.equalsIgnoreCase("text")) {
				lines = r.readNumber();
				if (parseDebug)
					System.out.println("DEBUG IMAP: lines " + lines);
				if (lines < 0)
					throw new ParsingException("BODYSTRUCTURE parse error: bad ``lines'' element");
			} else if (type.equalsIgnoreCase("message") && subtype.equalsIgnoreCase("rfc822")) {
				// Nested message
				processedType = NESTED;
				// The envelope comes next, but sadly Gmail handles nested
				// messages just like simple body parts and fails to return
				// the envelope and body structure of the message (sort of
				// like IMAP4 before rev1).
				r.skipSpaces();
				if (r.peekByte() == '(') { // the envelope follows
					envelope = new ENVELOPE(r);
					if (parseDebug)
						System.out.println("DEBUG IMAP: got envelope of nested message");
					BODYSTRUCTURE[] bs = { new BODYSTRUCTURE(r) };
					bodies = bs;
					lines = r.readNumber();
					if (parseDebug)
						System.out.println("DEBUG IMAP: lines " + lines);
					if (lines < 0)
						throw new ParsingException("BODYSTRUCTURE parse error: bad ``lines'' element");
				} else {
					if (parseDebug)
						System.out.println("DEBUG IMAP: " + "missing envelope and body of nested message");
				}
			} else {
				// Detect common error of including lines element on other types
				r.skipSpaces();
				byte bn = r.peekByte();
				if (Character.isDigit((char) bn)) // number
					throw new ParsingException("BODYSTRUCTURE parse error: server erroneously " + "included ``lines'' element with type " + type + "/" + subtype);
			}

			if (r.peekByte() == ')') {
				r.readByte();
				if (parseDebug)
					System.out.println("DEBUG IMAP: parse DONE");
				return; // done
			}

			// Optional extension data

			// MD5
			md5 = r.readString();
			if (r.readByte() == ')') {
				if (parseDebug)
					System.out.println("DEBUG IMAP: no MD5 DONE");
				return; // done
			}

			// Disposition
			byte b = r.readByte();
			if (b == '(') {
				disposition = r.readString();
				if (parseDebug)
					System.out.println("DEBUG IMAP: disposition " + disposition);
				dParams = parseParameters(r);
				if (parseDebug)
					System.out.println("DEBUG IMAP: dParams " + dParams);
				if (r.readByte() != ')') // eat the end ')'
					throw new ParsingException("BODYSTRUCTURE parse error: " + "missing ``)'' at end of disposition");
			} else if (b == 'N' || b == 'n') {
				if (parseDebug)
					System.out.println("DEBUG IMAP: disposition NIL");
				r.skip(2); // skip 'NIL'
			} else {
				throw new ParsingException("BODYSTRUCTURE parse error: " + type + "/" + subtype + ": " + "bad single part disposition, b " + b);
			}

			if (r.readByte() == ')') {
				if (parseDebug)
					System.out.println("DEBUG IMAP: disposition DONE");
				return; // done
			}

			// Language
			if (r.peekByte() == '(') { // a list follows
				language = r.readStringList();
				if (parseDebug)
					System.out.println("DEBUG IMAP: language len " + language.length);
			} else { // protocol is unnessarily complex here
				String l = r.readString();
				if (l != null) {
					String[] la = { l };
					language = la;
					if (parseDebug)
						System.out.println("DEBUG IMAP: language " + l);
				}
			}

			// RFC3501 defines an optional "body location" next,
			// but for now we ignore it along with other extensions.

			// Throw away any further extension data
			while (r.readByte() == ' ')
				parseBodyExtension(r);
			if (parseDebug)
				System.out.println("DEBUG IMAP: all DONE");
		}
	}

	public boolean isMulti() {
		return processedType == MULTI;
	}

	public boolean isSingle() {
		return processedType == SINGLE;
	}

	public boolean isNested() {
		return processedType == NESTED;
	}

	private ParameterList parseParameters(Response r) throws ParsingException {
		r.skipSpaces();

		ParameterList list = null;
		byte b = r.readByte();
		if (b == '(') {
			list = new ParameterList();
			do {
				String name = r.readString();
				if (parseDebug)
					System.out.println("DEBUG IMAP: parameter name " + name);
				if (name == null)
					continue; // Ignore null name
				String value = r.readString();
				if (parseDebug)
					System.out.println("DEBUG IMAP: parameter value " + value);
				list.set(name, value);
			} while (r.readByte() != ')');
			list.set(null, "DONE"); // XXX - hack
		} else if (b == 'N' || b == 'n') {
			if (parseDebug)
				System.out.println("DEBUG IMAP: parameter list NIL");
			r.skip(2);
		} else
			throw new ParsingException("Parameter list parse error");

		return list;
	}

	private void parseBodyExtension(Response r) throws ParsingException {
		r.skipSpaces();

		byte b = r.peekByte();
		if (b == '(') {
			r.skip(1); // skip '('
			do {
				parseBodyExtension(r);
			} while (r.readByte() != ')');
		} else if (Character.isDigit((char) b)) // number
			r.readNumber();
		else
			// nstring
			r.readString();
	}
}
